package com.inifitness.common.tools;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.util.StringUtils;

/**
 * 身份证号校验工具.
 *
 * @author sunjinfeng
 * @version 1.0.0
 * @since 2024/04/18 11:00:00
 */
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class IdCardNoUtil {

  private static final DateTimeFormatter SHORT_DATE_FORMATTER = DateTimeFormatter.ofPattern(
      "yyyyMMdd", Locale.CHINA);
  private static final int ID_CARD_LENGTH = 18;

  private static final int BIRTHDATE_START_INDEX = 6;
  private static final int BIRTHDATE_END_INDEX = 14;
  private static final int GENDER_START_INDEX = 16;
  private static final int GENDER_END_INDEX = 17;

  private static final int PROVINCE_START_INDEX = 0;
  private static final int PROVINCE_END_INDEX = 2;

  private static final int YEAR_START_INDEX = 6;
  private static final int YEAR_END_INDEX = 10;

  private static final int MONTH_START_INDEX = 10;
  private static final int MONTH_END_INDEX = 12;

  private static final int DAY_START_INDEX = 12;
  private static final int DAY_END_INDEX = 14;

  private static final int CHECK_BIT_INDEX = 17;
  private static final int GENDER_MODULO = 2;
  /**
   *     前6位地址码：^[1-6]\\d{5}
   *     年份：(18|19|20)\\d{2}
   *     月份：((0[1-9])|(1[0-2]))
   *     日期：(([0-2][1-9])|10|20|30|31)
   *     3位顺序码：\\d{3}
   *     检验码：[0-9X]
   */
  private static final String ID_CARD_NO_REGEX = "^[1-6]\\d{5}(18|19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9X]";

  private static final int[] AVAILABLE_PROVINCE_CODES = {11, 12, 13, 14, 15, 21, 22, 23, 31, 32, 33, 34, 35, 36, 37, 41,
      42, 43, 44, 45, 46, 50, 51, 52, 53, 53, 61, 62, 63, 64, 65, 83, 81, 82};

  private static final int[] WEIGHTING_FACTOR = {0, 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};

  private static final char[] CHECK_BITS = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
  /**
   * 根据身份证获取出生日期
   *
   * @param idCardNoValue 身份证号
   * @return 出生日期
   */
  public static LocalDate getBirthdate(final String idCardNoValue) {
    log.debug("从身份证号里面获取出生日期:{}", idCardNoValue);
    checkAndThrow(idCardNoValue);
    String birthday = idCardNoValue.substring(BIRTHDATE_START_INDEX, BIRTHDATE_END_INDEX);
    return LocalDate.parse(birthday, SHORT_DATE_FORMATTER);
  }

  /**
   * 根据证件号获取性别
   *
   * @param idCardNoValue 身份证号
   * @return 性别字段 0女 1男
   */
  public static int getGender(final String idCardNoValue) {
    log.debug("从身份证号里面获取性别:{}", idCardNoValue);
    checkAndThrow(idCardNoValue);
    return Integer.parseInt(idCardNoValue.substring(GENDER_START_INDEX, GENDER_END_INDEX)) % GENDER_MODULO;
  }

  public static void checkAndThrow(final String idCardNoValue){
    if (!check(idCardNoValue)){
      throw new IllegalArgumentException();
    }
  }

  public static boolean check(final String idCardNoValue) {
    String idCardNo = idCardNoValue.toUpperCase(Locale.CHINA);

    if (!StringUtils.hasLength(idCardNo) || idCardNo.length() != ID_CARD_LENGTH){
      return false;
    }

    if (!idCardNo.matches(ID_CARD_NO_REGEX)) {
      return false;
    }

    int province = Integer.parseInt(idCardNo.substring(PROVINCE_START_INDEX, PROVINCE_END_INDEX));
    boolean isProvinceValid = ArrayUtils.contains(AVAILABLE_PROVINCE_CODES, province);
    if (!isProvinceValid) {
      return false;
    }

    //生日校验：生日的时间不能比当前时间（指程序检测用户输入的身份证号码的时候）晚
    LocalDate birthdate = getBirthdate(idCardNo);
    boolean isBirthdateValid = birthdate.isBefore(LocalDate.now());
    if (!isBirthdateValid) {
      return false;
    }

    //生日校验：每个月的天数不一样（有的月份没有31），还要注意闰年的二月
    int year = Integer.parseInt(idCardNo.substring(YEAR_START_INDEX, YEAR_END_INDEX));
    boolean isLeap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
    int leap = isLeap ? 1 : 0;
    final int[] month = {0, 31, 28 + leap, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int mon = Integer.parseInt(idCardNo.substring(MONTH_START_INDEX, MONTH_END_INDEX));
    int day = Integer.parseInt(idCardNo.substring(DAY_START_INDEX, DAY_END_INDEX));
    if (day > month[mon]) {
      return false;
    }

    return idCardNo.charAt(CHECK_BIT_INDEX) == calculateCheckBitValue(idCardNo);
  }

  /**
   * 根据身份证号码的前17位计算校验码
   */
  public static char calculateCheckBitValue(final String idCardNo) {
    int res = 0;
    for (int i = 0; i < ID_CARD_LENGTH - 1; i++) {
      int t = idCardNo.charAt(i) - '0';
      res += (t * WEIGHTING_FACTOR[i + 1]);
    }
    return CHECK_BITS[res % 11];
  }
}
